Skip to content

Add OTEL gen-ai message format support for input/output message tracing(Manual instrument)#225

Open
fpfp100 wants to merge 2 commits intomainfrom
users/pefan/messages1
Open

Add OTEL gen-ai message format support for input/output message tracing(Manual instrument)#225
fpfp100 wants to merge 2 commits intomainfrom
users/pefan/messages1

Conversation

@fpfp100
Copy link
Copy Markdown
Contributor

@fpfp100 fpfp100 commented Mar 27, 2026

Summary

  • Add OTEL gen-ai semantic convention types (ChatMessage, OutputMessage, MessageRole, FinishReason, MessagePart discriminated union) with multi-modal support
  • Add message-utils.ts with conversion helpers and binary-search-based serialization with truncation sentinel for MAX_ATTRIBUTE_LENGTH
  • Move recordInputMessages/recordOutputMessages to protected base class with public overrides on InferenceScope, InvokeAgentScope, and OutputScope; ExecuteToolScope intentionally excluded
  • Widen method signatures to accept InputMessages/OutputMessages union (string[] auto-wrapped to OTEL format)
  • Update OutputResponse.messages type from string[] to OutputMessages (breaking change — backward compatible at runtime)

Test plan

  • message-utils.test.ts — 16 tests for conversion and serialization (truncation, boundaries, sentinel)
  • scope-messages.test.ts — 9 tests for scope message recording (string/structured input/output, complex part types, recordResponse)
  • output-scope.test.ts — 6 tests updated for new OTEL format
  • Hosting tests updated (output-logging-middleware, scope-utils)
  • Build passes for observability and observability-hosting packages

Sample Spans

Captured from basic-agent-sdk-sample via npm start + curl POST /api/messages. ConsoleSpanExporter output (5 spans):

Click to expand full console output
{
  resource: {
    attributes: {
      'host.name': 'pefan4-0',
      'host.arch': 'amd64',
      'host.id': '3dc679db-f652-4002-98b7-5e05e5071507',
      'process.pid': 61028,
      'process.executable.name': 'C:\\Program Files\\Git\\bin\\..\\usr\\bin\\bash.exe',
      'process.executable.path': 'C:\\Program Files\\nodejs\\node.exe',
      'process.command_args': [
        'C:\\Program Files\\nodejs\\node.exe',
        'D:\\repos\\sdk\\Agent365-nodejs\\tests-agent\\basic-agent-sdk-sample\\dist\\index.js'
      ],
      'process.runtime.version': '20.18.3',
      'process.runtime.name': 'nodejs',
      'process.runtime.description': 'Node.js',
      'process.command': 'D:\\repos\\sdk\\Agent365-nodejs\\tests-agent\\basic-agent-sdk-sample\\dist\\index.js',
      'process.owner': 'pefan',
      'service.name': 'TypeScript Sample Agent-1.0.0'
    }
  },
  instrumentationScope: { name: 'Agent365Sdk', version: undefined, schemaUrl: undefined },
  traceId: '2278d47a2b9d71d87c5150eb4f886830',
  parentSpanContext: {
    traceId: '2278d47a2b9d71d87c5150eb4f886830',
    spanId: 'fa8f149ce5c10dac',
    traceFlags: 1,
    traceState: undefined
  },
  traceState: undefined,
  name: 'Chat gpt-4',
  id: '150c764d25bfc6fe',
  kind: 2,
  timestamp: 1774573242192000,
  duration: 4007391.6,
  attributes: {
    'gen_ai.operation.name': 'Chat',
    'telemetry.sdk.name': 'A365ObservabilitySDK',
    'telemetry.sdk.language': 'nodejs',
    'telemetry.sdk.version': '0.0.0-placeholder',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.conversation.id': 'conv-gdpr-001',
    'microsoft.conversation.item.link': 'https://agent365.contoso.com',
    'gen_ai.agent.name': 'ComplianceAssistant',
    'gen_ai.agent.description': 'assistant',
    'microsoft.session.description': 'Initial onboarding session',
    'microsoft.agent.user.id': 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
    'user.id': 'c8a7b6d5-e4f3-2110-9876-543210fedcba',
    'user.name': 'Jane Smith',
    'user.email': 'jane.smith@contoso.com',
    'microsoft.channel.name': 'msteams',
    'gen_ai.agent.id': '30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
    'gen_ai.request.model': 'gpt-4',
    'gen_ai.provider.name': 'openai',
    'gen_ai.usage.input_tokens': 52,
    'gen_ai.usage.output_tokens': 85,
    'gen_ai.response.finish_reasons': [ 'stop' ],
    'gen_ai.input.messages': '[{"role":"system","parts":[{"type":"text","content":"You are a compliance analysis assistant."}]},{"role":"user","parts":[{"type":"text","content":"Analyze the following compliance query: What are the GDPR data retention policies?"}]}]',
    'gen_ai.output.messages': '[{"role":"assistant","parts":[{"type":"reasoning","content":"The user is asking about compliance policies. Let me check the relevant documents."},{"type":"text","content":"Based on my analysis of \\"What are the GDPR data retention policies?\\", I recommend checking policy documents XYZ and ensuring proper data handling procedures."}],"finish_reason":"stop"}]'
  },
  status: { code: 0 },
  events: [],
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'upstream_inference' }
    },
    {
      context: {
        traceId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
        spanId: 'ffffffffffffffff',
        traceFlags: 1
      },
      attributes: {
        'link.type': 'follows_from',
        'link.reason': 'prompt_cache_hit'
      }
    }
  ]
}
{
  resource: {
    attributes: {
      'host.name': 'pefan4-0',
      'service.name': 'TypeScript Sample Agent-1.0.0'
    }
  },
  instrumentationScope: { name: 'Agent365Sdk', version: undefined, schemaUrl: undefined },
  traceId: '2278d47a2b9d71d87c5150eb4f886830',
  parentSpanContext: {
    traceId: '2278d47a2b9d71d87c5150eb4f886830',
    spanId: 'fa8f149ce5c10dac',
    traceFlags: 1,
    traceState: undefined
  },
  traceState: undefined,
  name: 'execute_tool send-email',
  id: '14a40a48ee01cdbe',
  kind: 0,
  timestamp: 1774573246201000,
  duration: 2014428.9,
  attributes: {
    'gen_ai.operation.name': 'execute_tool',
    'telemetry.sdk.name': 'A365ObservabilitySDK',
    'telemetry.sdk.language': 'nodejs',
    'telemetry.sdk.version': '0.0.0-placeholder',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.conversation.id': 'conv-gdpr-001',
    'gen_ai.agent.id': '30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
    'gen_ai.agent.name': 'ComplianceAssistant',
    'gen_ai.tool.name': 'send-email',
    'gen_ai.tool.call.arguments': '{"recipient":"user@example.com","subject":"Hello","body":"Test email"}',
    'gen_ai.tool.type': 'function',
    'gen_ai.tool.call.id': 'tool-1774573246200',
    'gen_ai.tool.description': 'Sends an email to the specified recipient',
    'gen_ai.tool.call.result': 'Email sent successfully to user@example.com'
  },
  status: { code: 0 },
  events: [],
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'tool_dispatch' }
    }
  ]
}
{
  resource: {
    attributes: {
      'host.name': 'pefan4-0',
      'service.name': 'TypeScript Sample Agent-1.0.0'
    }
  },
  instrumentationScope: { name: 'Agent365Sdk', version: undefined, schemaUrl: undefined },
  traceId: '2278d47a2b9d71d87c5150eb4f886830',
  parentSpanContext: {
    traceId: '2278d47a2b9d71d87c5150eb4f886830',
    spanId: 'fa8f149ce5c10dac',
    traceFlags: 1,
    traceState: undefined
  },
  traceState: undefined,
  name: 'output_messages 30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
  id: '13df9fd2c1d4c278',
  kind: 2,
  timestamp: 1774573248216000,
  duration: 381.1,
  attributes: {
    'gen_ai.operation.name': 'output_messages',
    'telemetry.sdk.name': 'A365ObservabilitySDK',
    'telemetry.sdk.language': 'nodejs',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.conversation.id': 'conv-gdpr-001',
    'gen_ai.agent.id': '30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
    'gen_ai.agent.name': 'ComplianceAssistant',
    'gen_ai.output.messages': '[{"role":"assistant","parts":[{"type":"text","content":"LLM: Based on my analysis of \\"What are the GDPR data retention policies?\\", I recommend checking policy documents XYZ and ensuring proper data handling procedures."}]},{"role":"assistant","parts":[{"type":"text","content":"Tool: Email sent successfully to user@example.com"}]},{"role":"assistant","parts":[{"type":"text","content":"Follow-up message"}]}]'
  },
  status: { code: 0 },
  events: [],
  links: []
}
{
  resource: {
    attributes: {
      'host.name': 'pefan4-0',
      'service.name': 'TypeScript Sample Agent-1.0.0'
    }
  },
  instrumentationScope: { name: 'Agent365Sdk', version: undefined, schemaUrl: undefined },
  traceId: '2278d47a2b9d71d87c5150eb4f886830',
  parentSpanContext: {
    traceId: '2278d47a2b9d71d87c5150eb4f886830',
    spanId: 'fa8f149ce5c10dac',
    traceFlags: 1,
    traceState: undefined
  },
  traceState: undefined,
  name: 'output_messages 30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
  id: '829300a1caf2cf13',
  kind: 2,
  timestamp: 1774573248216000,
  duration: 369,
  attributes: {
    'gen_ai.operation.name': 'output_messages',
    'telemetry.sdk.name': 'A365ObservabilitySDK',
    'telemetry.sdk.language': 'nodejs',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.conversation.id': 'conv-gdpr-001',
    'gen_ai.agent.id': '30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
    'gen_ai.agent.name': 'ComplianceAssistant',
    'gen_ai.output.messages': '[{"role":"assistant","parts":[{"type":"text","content":"LLM Response: Based on my analysis of \\"What are the GDPR data retention policies?\\", I recommend checking policy documents XYZ and ensuring proper data handling procedures."}],"finish_reason":"stop"},{"role":"assistant","parts":[{"type":"text","content":"Tool Response: Email sent successfully to user@example.com"}]},{"role":"assistant","parts":[{"type":"text","content":"Streaming chunk appended"}],"finish_reason":"stop"}]'
  },
  status: { code: 0 },
  events: [],
  links: []
}
{
  resource: {
    attributes: {
      'host.name': 'pefan4-0',
      'service.name': 'TypeScript Sample Agent-1.0.0'
    }
  },
  instrumentationScope: { name: 'Agent365Sdk', version: undefined, schemaUrl: undefined },
  traceId: '2278d47a2b9d71d87c5150eb4f886830',
  parentSpanContext: undefined,
  traceState: undefined,
  name: 'invoke_agent',
  id: 'fa8f149ce5c10dac',
  kind: 2,
  timestamp: 1774573242186000,
  duration: 6030686.6,
  attributes: {
    'gen_ai.operation.name': 'invoke_agent',
    'telemetry.sdk.name': 'A365ObservabilitySDK',
    'telemetry.sdk.language': 'nodejs',
    'telemetry.sdk.version': '0.0.0-placeholder',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.conversation.id': 'conv-gdpr-001',
    'gen_ai.agent.id': '30ed5699-b157-4e87-bb45-9b0cfb13b8e5',
    'gen_ai.agent.name': 'ComplianceAssistant',
    'gen_ai.agent.description': 'assistant',
    'microsoft.agent.user.id': 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
    'user.id': 'c8a7b6d5-e4f3-2110-9876-543210fedcba',
    'user.name': 'Jane Smith',
    'user.email': 'jane.smith@contoso.com',
    'microsoft.channel.name': 'msteams',
    'server.address': 'https://agent365.contoso.com',
    'server.port': 56150,
    'gen_ai.input.messages': '[{"role":"user","parts":[{"type":"text","content":"What are the GDPR data retention policies?"}]}]',
    'gen_ai.output.messages': '[{"role":"assistant","parts":[{"type":"text","content":"LLM Response: Based on my analysis of \\"What are the GDPR data retention policies?\\", I recommend checking policy documents XYZ and ensuring proper data handling procedures."}],"finish_reason":"stop"},{"role":"assistant","parts":[{"type":"text","content":"Tool Response: Email sent successfully to user@example.com"}]}]'
  },
  status: { code: 0 },
  events: [],
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'upstream_request' }
    },
    {
      context: {
        traceId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
        spanId: 'aaaaaaaaaaaaaaaa',
        traceFlags: 0
      },
      attributes: { 'link.type': 'follows_from', 'link.reason': 'retry' }
    },
    {
      context: {
        traceId: 'cccccccccccccccccccccccccccccccc',
        spanId: 'dddddddddddddddd',
        traceFlags: 1
      },
      attributes: {
        'link.type': 'causal',
        'link.reason': 'parent_orchestrator',
        'link.index': 0
      }
    }
  ]
}

🤖 Generated with Claude Code

- Add OTEL gen-ai semantic convention types (ChatMessage, OutputMessage,
  MessageRole, FinishReason, MessagePart discriminated union with support
  for text, tool calls, reasoning, blob, file, URI, and generic parts)
- Add message-utils with conversion helpers and binary-search-based
  serialization with truncation sentinel for MAX_ATTRIBUTE_LENGTH
- Move recordInputMessages/recordOutputMessages to protected base class
  (OpenTelemetryScope) with public overrides on InferenceScope,
  InvokeAgentScope, and OutputScope; ExecuteToolScope intentionally excluded
- Widen method signatures to accept InputMessages/OutputMessages union
  (string[] auto-wrapped to OTEL format, structured objects pass through)
- Update OutputResponse.messages type from string[] to OutputMessages
- Update design doc, CHANGELOG, and add comprehensive test coverage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fpfp100 fpfp100 requested a review from a team as a code owner March 27, 2026 00:39
Copilot AI review requested due to automatic review settings March 27, 2026 00:39
@fpfp100 fpfp100 requested a review from a team as a code owner March 27, 2026 00:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenTelemetry Gen-AI semantic-convention message support to the observability SDK, enabling structured (multi-part / multi-modal) input/output message tracing with safe serialization under span attribute limits.

Changes:

  • Introduces OTEL gen-ai message types (ChatMessage, OutputMessage, MessagePart, MessageRole, FinishReason, etc.) and widens scope APIs to accept InputMessages/OutputMessages unions.
  • Adds message-utils.ts helpers for string-to-OTEL conversion and length-capped JSON serialization with truncation sentinel.
  • Refactors message recording into OpenTelemetryScope (protected) with public overrides on relevant scopes; updates tests/docs/changelog accordingly.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/observability/tracing/message-utils.test.ts Adds unit tests for conversion helpers and serialization/truncation behavior.
tests/observability/extension/hosting/scope-utils.test.ts Updates expectations to new structured OTEL input message JSON format.
tests/observability/extension/hosting/output-logging-middleware.test.ts Updates output message attribute assertions to structured OTEL format.
tests/observability/core/scope-messages.test.ts Adds integration-style tests verifying scope message recording for strings + structured parts.
tests/observability/core/output-scope.test.ts Updates OutputScope tests for structured OTEL output messages and new unions.
packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts Stores accumulated output messages as OutputMessage[] and serializes via serializeMessages.
packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts Adds protected recordInputMessages/recordOutputMessages using message-utils conversion + serialization.
packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts Widens message-recording method signatures and delegates to base implementations.
packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts Removes JSON.stringify-based message recording and delegates to base OTEL message recording.
packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts No functional changes (formatting-only).
packages/agents-a365-observability/src/tracing/message-utils.ts New helpers for type-guarding, conversion, and capped JSON serialization with sentinel truncation.
packages/agents-a365-observability/src/tracing/contracts.ts Adds OTEL gen-ai message types and changes OutputResponse.messages to OutputMessages.
packages/agents-a365-observability/src/index.ts Exports new OTEL gen-ai message types from the public package surface.
packages/agents-a365-observability/docs/design.md Documents new message format, serialization, and scope visibility decisions.
CHANGELOG.md Notes the breaking type change and new OTEL message format additions.

return '[' + serialized.slice(0, bestCount).join(',') + ',' + sentinel + ']';
}

return truncateValue('[' + serialized[0] + ']');
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeMessages() falls back to truncateValue('[' + serialized[0] + ']') when even a single message can’t fit. This truncates an arbitrary JSON string, which can easily produce an invalid/non-parseable JSON array in the GEN_AI_*_MESSAGES span attributes. Consider keeping the attribute value valid JSON by truncating message part content before JSON.stringify, or by returning a valid sentinel-only array/object when a single message exceeds MAX_ATTRIBUTE_LENGTH.

Suggested change
return truncateValue('[' + serialized[0] + ']');
// If even a single message cannot fit, fall back to a small, valid JSON array
// containing only a sentinel message indicating full truncation.
const sentinelAll = buildSentinel(total, total);
return '[' + sentinelAll + ']';

Copilot uses AI. Check for mistakes.
…re-export

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fpfp100 fpfp100 changed the title Add OTEL gen-ai message format support for input/output message tracing Add OTEL gen-ai message format support for input/output message tracing(Manual instrument) Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants